<?php
declare (strict_types = 1);

namespace App\Utils\Services;

use App\Constants\ErrorCode;
use App\Exception\InternalException;
use App\Exception\InvalidRequestException;
use App\Model\Model;
use Hyperf\Context\Context;
use Hyperf\Coroutine\Coroutine;
use Hyperf\Collection\Collection;
use Hyperf\Contract\LengthAwarePaginatorInterface;
use Hyperf\Database\Model\Builder;
use Hyperf\Database\Schema\Schema;
use Hyperf\DbConnection\Db;
use Hyperf\Collection\Arr;
use Generator;
use Hyperf\Database\Model\Collection as ModelCollection;
use function Hyperf\Support\make;
use function Hyperf\Translation\__;
use function Hyperf\Coroutine\defer;

/**
 * Class BaseServices
 * @method static static query(bool $cache = false) 准备查询
 * @method static static search(array $data=[], string $alias='') 查询器
 * @method static static whereNull($columns, $boolean = 'and', $not = false) 查询null值
 * @method static static whereNotNull($columns, $boolean = 'and') 查询not null值
 * @method static static orderBy($column, $direction = 'asc') 排序
 * @method static static orderByRaw($sql, $bindings = []) 原始排序
 * @method static static where($column, $operator = null, $value = null, $boolean = 'and') 查询条件
 * @method static static whereIn($column, $values, $boolean = 'and', $not = false) 查询条件
 * @method static static whereNotIn($column, $values, $boolean = 'and') Not in
 * @method static static whereTime($column, $operator, $value = null, $boolean = 'and') 查询时间是否相等 whereTime('created_at', '=', '11:20:45')
 * @method static static orWhereTime($column, $operator, $value = null) 时间查询 OR
 * @method static static whereDate($column, $operator, $value = null, $boolean = 'and') 查询日期是否相等 whereDate('created_at', '2016-12-31')
 * @method static static orWhereDate($column, $operator, $value = null) 日期查询 OR
 * @method static static whereDay($column, $operator, $value = null, $boolean = 'and') 查询天是否相等 whereDay('created_at', '31')
 * @method static static orWhereDay($column, $operator, $value = null) 天查询 OR
 * @method static static whereMonth($column, $operator, $value = null, $boolean = 'and') 查询月是否相等 whereMonth('created_at', '12')
 * @method static static orWhereMonth($column, $operator, $value = null) 周查询 OR
 * @method static static whereYear($column, $operator, $value = null, $boolean = 'and') 查询年是否相等 whereYear('created_at', '2016')
 * @method static static orWhereYear($column, $operator, $value = null) 年查询 OR
 * @method static static with($relations) 关联查询
 * @method static static withMin($relation, string $column) 获取关联最小值
 * @method static static withMax($relation, string $column) 获取关联最大值
 * @method static static join($table, $first, $operator = null, $second = null, $type = 'inner', $where = false) JOIN查询
 * @method static static leftJoin($table, $first, $operator = null, $second = null) LEFT JOIN查询
 * @method static static rightJoin($table, $first, $operator = null, $second = null) RIGHT JOIN查询
 * @method static static rightJoinWhere($table, $first, $operator, $second) RIGHT JOIN WHERE
 * @method static static leftJoinWhere($table, $first, $operator, $second) LEFT JOIN WHERE
 * @method static static whereRaw($sql, $bindings = [], $boolean = 'and') 原生语句
 * @method static static select($columns = []) SELECT查询
 * @method static static groupBy(...$groups) 分组查询
 * @method static static having($column, $operator = null, $value = null, $boolean = 'and') Having查询
 * @method static static orHaving($column, $operator = null, $value = null) Or Having查询
 * @method static static orWhere($column, $operator = null, $value = null) Or Where查询
 * @method static static withoutGlobalScope($scope) 取消全局作用域
 * @method static static withoutGlobalScopes($scope=null) 取消所有全局作用域
 * @method static static addSelect($column) 向查询中添加新的选择列
 * @method static static whereJsonContains($column, $value, $boolean = 'and', $not = false) 查询json数组中是否存在值
 * @method static static orWhereJsonContains($column, $value) OR 查询json数组中是否存在值
 * @method static static whereJsonDoesntContain($column, $value, $boolean = 'and') 查询json数组中是否不存在值
 * @method static static orWhereJsonDoesntContain($column, $value) Or 查询json数组中是否不存在值
 * @method static static whereJsonLength($column, $operator, $value = null, $boolean = 'and') 查询json数组长度
 * @method static static orWhereJsonLength($column, $operator, $value = null) Or 查询json数组长度
 * @method static static has($relation, $operator = '>=', $count = 1, $boolean = 'and', $callback = null) 向查询中添加关系计数/存在条件
 * @method static static orHas($relation, $operator = '>=', $count = 1) or 向查询中添加关系计数/存在条件
 * @method static static whereHas($relation, $callback = null, $operator = '>=', $count = 1) 将关系计数/存在条件添加到带有where子句的查询中。
 * @method static static orWhereHas($relation, $callback = null, $operator = '>=', $count = 1) Or 将关系计数/存在条件添加到带有where子句的查询中。
 * @method static static doesntHave($relation, $boolean = 'and', $callback = null) 向查询中添加关系计数/存在条件。
 * @method static static orDoesntHave($relation) Or 向查询中添加关系计数/存在条件。
 * @method static static whereDoesntHave($relation, $callback = null) 将关系计数/存在条件添加到带有where子句的查询中。
 * @method static static orWhereDoesntHave($relation, $callback = null) Or 将关系计数/存在条件添加到带有where子句的查询中。
 * @method static static withCount($relations) 添加子选择查询以计算关系。
 * @method static static from($table) 设置查询表
 * @method static static fromRaw($expression, $bindings = []) 将原始from子句添加到查询中。
 * @method static static fromSub($query, $as) 从子查询获取“from”。
 * @method static static whereColumn($first, $operator = null, $second = null, $boolean = 'and') 添加一个将两列与查询进行比较的“where”子句。
 * @method static static selectSub($query, $as) 子查询
 * @method static static limit($value) 条目数
 * @package App\Utils\Services
 */
abstract class BaseServices implements ServicesInterface {

    public function __construct()
    {

    }

    /**
     * 模型
     * @return mixed
     */
    public abstract function model():string;

    /**
     * 生成模型
     * @return static
     */
    public function makeModel():static
    {
        return $this->setModel($this->model());
    }

    /**
     * 模型设置
     * @param $eloquentModel
     * @return $this
     */
    public function setModel($eloquentModel):static
    {
        Context::set($this->model(), make($eloquentModel));
        if (Context::has($this->model().':suffix')) {
            $this->suffix(Context::get($this->model().':suffix'));
        }
        return $this;
    }

    /**
     * 获取当前模型
     * @return mixed|Model|Builder
     */
    public function getModel()
    {
        if (false === Context::has($this->model())) {
            $this->makeModel();
        }
        return Context::get($this->model());
    }

    /**
     * 获取查询
     * @return \Hyperf\Database\Query\Builder
     */
    public function getQuery()
    {
        return $this->getModel()->getQuery();
    }

    /**
     * 获取后缀
     * @param string $suffix
     * @return $this
     */
    public function suffix(string $suffix)
    {
        Context::set($this->model().':suffix', $suffix);
        Context::set($this->model(), $this->getModel()->setTable($this->getModel()->getTable().'_'.$suffix));

//        if (!Schema::hasTable($this->getModel()->getTable())) { # 如果表不存在则创建表
//
//        }

        return $this;
    }

    /**
     * Like查询
     * @param $column
     * @param null $value
     * @param string $boolean
     * @return $this
     */
    public function whereLike($column, $value = null, $boolean = 'and'):static
    {
        Context::set($this->model(), $this->getModel()->where($column, 'like', $value, $boolean));

        return $this;
    }

    /**
     * json数字排序
     * @param $column
     * @param string $direction
     * @return $this
     */
    public function orderByJsonNumber($column, $direction = 'asc'):static
    {
        Context::set($this->model(), $this->getModel()->orderByRaw(
            "CAST({$this->getQuery()->grammar->wrap($column)} AS UNSIGNED) {$direction}"
        ));

        return $this;
    }

    /**
     * 调用
     * @param $name
     * @param $arguments
     * @return $this
     */
    public function __call($name, $arguments):static
    {
        try {
            Context::set($this->model(), $this->getModel()->{$name}(...$arguments));
        } catch (\Throwable $throwable) {
            $this->makeModel();
            logs($throwable);
            logs($throwable->getTraceAsString());
            throw new InternalException();
        }

        return $this;
    }

    /**
     * 查询XY轴
     * @param array $raw 查询列
     * @return $this
     */
    public function selectGeometry(array $raw=['*']):static
    {
        $model = make($this->model());
        if ($model->geometry) {
            foreach ($model->geometry as $column) {
                $raw[] = Db::raw("st_x(`{$column}`) as `{$column}_x`");
                $raw[] = Db::raw("st_y(`{$column}`) as `{$column}_y`");
            }
            Context::set($this->model(), $this->getModel()->addSelect($raw));
        }

        return $this;
    }

    /**
     * 分页
     * @param $data
     * @param ...$argument
     * @return LengthAwarePaginatorInterface|\Hyperf\Database\Model\Collection|static[]
     */
    public function paginate(...$argument)
    {
        try {
            if (Arr::get($argument, 0) === 'all') {
                $res = $this->getModel()->get();
            } else {
                $res = $this->getModel()->paginate(...$argument);
            }
        } catch (\Throwable $throwable) {
            $this->makeModel();
            throw $throwable;
        }
        $this->makeModel();

        return $res;
    }

    /**
     * 简洁分页
     * @param $data
     * @param ...$argument
     * @return LengthAwarePaginatorInterface|\Hyperf\Database\Model\Collection|static[]
     */
    public function simplePaginate(...$argument)
    {
        try {
            if (Arr::get($argument, 0) === 'all') {
                $res = $this->getModel()->get();
            } else {
                $res = $this->getModel()->simplePaginate(...$argument);
            }
        } catch (\Throwable $throwable) {
            $this->makeModel();
            throw $throwable;
        }
        $this->makeModel();

        return $res;
    }

    /**
     * 获取数据
     * @param array|string[] $columns
     * @return Model[]|\Hyperf\Database\Model\Collection|mixed
     */
    public function get($columns=['*'])
    {
        $res = $this->getModel()->get($columns);
        $this->makeModel();

        return $res;
    }

    /**
     * 单条数据查询
     * @param array|string[] $columns
     * @param bool $throw
     * @return Model|\Hyperf\Database\Model\Model|mixed|object|null
     * @throws \Throwable
     */
    public function first($columns = ['*'], bool $throw=false)
    {
        // TODO: Implement first() method.
        if (true === $throw) {
            try {
                $res = $this->getModel()->firstOrFail($columns);
            } catch (\Throwable $throwable) {
                logs($this->getModel()->toSql());
                $this->makeModel();
                throw $throwable;
            }
        } else {
            $res = $this->getModel()->first($columns);
        }
        $this->makeModel();

        return $res;
    }

    /**
     * 单条数据查询,不存在则抛出异常
     * @param string[] $columns
     * @return Model|\Hyperf\Database\Model\Model|mixed|object|null
     * @throws \Throwable
     */
    public function firstOrFail($columns = ['*'])
    {
        return $this->first($columns, true);
    }

    /**
     * 根据主键查询数据
     * @param $id
     * @param array|string[] $columns
     * @param bool $throw
     * @return Model|Model[]|\Hyperf\Database\Model\Collection|\Hyperf\Database\Model\Model|mixed|null
     */
    public function find($id, $columns = ['*'], bool $throw=false)
    {
        // TODO: Implement find() method.
        if (true === $throw) {
            try {
                $res = $this->getModel()->findOrFail($id, $columns);
            } catch (\Throwable $throwable) {
                logs($this->getModel()->toSql());
                $this->makeModel();
                throw new InvalidRequestException(__('message.model_not_found'), ErrorCode::SERVER_NOT_FOUND);
            }
        } else {
            $res = $this->getModel()->find($id, $columns);
        }
        $this->makeModel();

        return $res;
    }

    /**
     * 查询缓存
     * @param $ids
     * @param bool $throw
     * @return Model|ModelCollection|\Hyperf\Database\Model\Model|null
     * @throws \Throwable
     */
    public function findFromCache($ids, bool $throw=false)
    {
        try {
            if (is_array($ids)) {
                $res = $this->getModel()->findManyFromCache($ids);
                if (true === $throw && $res->isEmpty()) {
                    throw new InvalidRequestException(__('message.model_not_found'), ErrorCode::SERVER_NOT_FOUND);
                }
            } else {
                $res = $this->getModel()->findFromCache($ids);
                if (true === $throw && !$res) {
                    throw new InvalidRequestException(__('message.model_not_found'), ErrorCode::SERVER_NOT_FOUND);
                }
            }
        } catch (\Throwable $throwable) {
            $this->makeModel();
            throw $throwable;
        }
        $this->makeModel();

        return $res;
    }

    /**
     * 根据主键查询数据,不存在则抛出异常
     * @param $id
     * @param string[] $columns
     * @return Model|Model[]|\Hyperf\Database\Model\Collection|\Hyperf\Database\Model\Model|mixed|null
     */
    public function findOrFail($id, $columns = ['*'])
    {
        return $this->find($id, $columns, true);
    }

    /**
     * 根据某个字段查询
     * @param string $field
     * @param $value
     * @return \Hyperf\Database\Model\Builder|\Hyperf\Database\Model\Model|object|null
     */
    public function findBy(string $field, $value, bool $throw=false)
    {
        $res = $this->where($field, $value)->first(['*'], $throw);
        $this->makeModel();

        return $res;
    }

    /**
     * 统计
     * @param string $columns
     * @return int
     */
    public function count($columns = '*'):int
    {
        // TODO: Implement count() method.
        $count = $this->getModel()->count($columns);
        $this->makeModel();

        return $count;
    }

    /**
     * 总数
     * @param $column
     * @return mixed
     */
    public function sum($column):mixed
    {
        // TODO: Implement sum() method.
        $sum = $this->getModel()->sum($column);
        $this->makeModel();

        return $sum;
    }

    /**
     * 最大值
     * @param $column
     * @return mixed
     */
    public function max($column):mixed
    {
        // TODO: Implement max() method.
        $max = $this->getModel()->max($column);
        $this->makeModel();

        return $max;
    }

    /**
     * 最小值
     * @param $column
     * @return mixed
     */
    public function min($column):mixed
    {
        // TODO: Implement min() method.
        $min = $this->getModel()->min($column);
        $this->makeModel();

        return $min;
    }

    /**
     * 求和
     * @param $column
     * @return mixed
     */
    public function avg($column):mixed
    {
        // TODO: Implement avg() method.
        $avg = $this->getModel()->avg($column);
        $this->makeModel();

        return $avg;
    }

    /**
     * 数据切片
     * @param $count
     * @param $callback
     * @return bool
     */
    public function chunk($count, $callback):bool
    {
        // TODO: Implement chunk() method.
        $chunk = $this->getModel()->chunk($count, $callback);
        $this->makeModel();

        return $chunk;
    }

    /**
     * 根据ID切片
     * @param $count
     * @param $callback
     * @return bool
     */
    public function chunkById($count, $callback, $column = null, $alias = null):bool
    {
        // TODO: Implement chunk() method.
        $chunk = $this->getModel()->chunkById($count, $callback, $column, $alias);
        $this->makeModel();

        return $chunk;
    }

    /**
     * 游标查询
     * @return Generator
     */
    public function cursor():Generator
    {
        $cursor = $this->getModel()->cursor();
        $this->makeModel();

        return $cursor;
    }

    /**
     * 获取列
     * @param $column
     * @param null $key
     * @return Collection
     */
    public function pluck($column, $key = null):Collection
    {
        // TODO: Implement pluck() method.
        $pluck = $this->getModel()->pluck($column, $key);
        $this->makeModel();

        return $pluck;
    }

    /**
     * 获取值
     * @param $column
     * @return mixed
     */
    public function value($column):mixed
    {
        $value = $this->getModel()->value($column);
        $this->makeModel();

        return $value;
    }

    /**
     * 新增/编辑
     * @param array $options
     * @return bool
     */
    public function save(array $options = []): bool
    {
        // TODO: Implement save() method.
        $save = $this->getModel()->save($options);
        $this->makeModel();

        return $save;
    }

    /**
     * 编辑
     * @param array $attributes
     * @param array $options
     * @return bool|int
     */
    public function update(array $attributes = [], array $options = []):bool|int
    {
        // TODO: Implement update() method.
        $update = $this->getModel()->update($attributes, $options);
        $this->makeModel();

        return $update;
    }

    /**
     * 自增
     * @param $column
     * @param int $amount
     * @param array $extra
     * @return int
     */
    public function inc($column, $amount = 1, array $extra = []):int
    {
        $inc = $this->getModel()->increment($column, $amount, $extra);
        $this->makeModel();

        return $inc;
    }

    /**
     * 自减
     * @param $column
     * @param int $amount
     * @param array $extra
     * @return int
     */
    public function dec($column, $amount = 1, array $extra = []):int
    {
        $inc = $this->getModel()->decrement($column, $amount, $extra);
        $this->makeModel();

        return $inc;
    }

    /**
     * 新增
     * @param array $attributes
     * @return Model|\Hyperf\Database\Model\Model|mixed
     */
    public function create($attributes = [])
    {
        $create = $this->getModel()->create($attributes);
        $this->makeModel();

        return $create;
    }

    /**
     * 批量新增
     * @param $values
     * @return ModelCollection
     */
    public function createAll($values):ModelCollection
    {
        $collect = [];
        Db::transaction(function ()use($values, &$collect){
            foreach ($values as $value) {
                $model = $this->create($value);
                if (false === $model->exists) {
                    throw new InternalException(__('message.stored.failed'));
                }
                $collect[] = $model;
            }
        });

        $collection = $this->getModel()->newCollection($collect);
        $this->makeModel();
        return $collection;
    }

    /**
     * 批量新增
     * @tips 不走模型事件慎用
     * @param array $values
     * @return bool
     */
    public function insert(array $values = []):bool
    {
        $insert = $this->getModel()->insert($values);
        $this->makeModel();

        return $insert;
    }

    /**
     * 删除
     * @param null $ids
     * @return bool|int|null
     * @throws \Exception
     */
    public function delete($ids=null):bool|null|int
    {
        // TODO: Implement delete() method.
        if (null === $ids) {
            $delete = $this->getModel()->delete();
        } else {
            $delete = $this->getModel()->destroy($ids);
        }
        $this->makeModel();

        return $delete;
    }

    /**
     * 判断数据是否存在
     * @return bool
     */
    public function exists():bool
    {
        $exists = $this->getModel()->exists();
        $this->makeModel();

        return $exists;
    }

    /**
     * 判断数据是否不存在
     * @return bool
     */
    public function doesntExist():bool
    {
        $doesntExists = $this->getModel()->doesntExist();
        $this->makeModel();

        return $doesntExists;
    }

    /**
     * 拖拽排序
     * @param Model $model
     * @param int $id
     * @param string $parentField
     * @return bool
     */
    public function sort(Model $model, int $id, string $parentField = '') :bool
    {
        if ($model->id == $id) {
            return false;
        }
        $oldIndex = $model->sort;

        $map = [];
        if ($parentField) {
            $map[] = [$parentField, '=', $model->$parentField>0?$model->$parentField:0];
        }
        $newIndex = $this->where('id', $id)->where($map)->value('sort');
        if (!$newIndex) {
            return false;
        }

        if ($oldIndex > $newIndex) {
            $action = 'inc';
            $map[] = [function($query)use($newIndex, $oldIndex){
                $query->whereBetween('sort', [$newIndex, $oldIndex]);
            }];
        } else {
            $action = 'dec';
            $map[] = [function($query)use($newIndex, $oldIndex){
                $query->whereBetween('sort', [$oldIndex==0?1:$oldIndex, $newIndex]);
            }];
        }

        Db::beginTransaction();
        $res = $this->where($map)->$action('sort');
        $model->sort = $newIndex;
        if (!$res || !$model->save()) {
            Db::rollBack();
            return false;
        }
        Db::commit();
        return true;
    }

    /**
     * 一对多同步
     * @param Model $parentModel
     * @param array $data
     * @param string $foreignKey 关联主键
     * @param string|null $childrenPk 子集ID,用来判断字段是否存在
     * @return Model
     * @throws \Exception
     */
    public function sync(Model $parentModel, array $data, string $foreignKey, ?string $childrenPk=null, array $map=[]):Model
    {
        /** @var string $parentPk 父级主键 */
        $parentPk = $parentModel->getKeyName(); // id
        /** @var string $childrenPk 子集主键 */
        $childrenPk = $childrenPk?:$this->getModel()->getKeyName(); // id
        $this->where($foreignKey, $parentModel->$parentPk)
            ->whereNotIn($childrenPk, array_filter(array_column($data, $childrenPk)))
            ->where($map)
            ->delete();

        foreach ($data as $datum) {
            if ($this->getModel()->getKeyName() === $childrenPk) {
                if (Arr::get($datum, $childrenPk)) {
                    $childrenModel = $this->where($childrenPk, $datum[$childrenPk])->where($map)->firstOrFail();
                    if (!$childrenModel->fill($datum)->save()) {
                        throw new InternalException(__('message.updated.failed'));
                    }
                } else {
                    $datum[$foreignKey] = $parentModel->$parentPk;
                    $childrenModel = $this->create($datum);
                    if (!$childrenModel->exists) {
                        throw new InternalException(__('message.updated.failed'));
                    }
                }
            } else {
                $childrenModel = $this->where($childrenPk, $datum[$childrenPk])->where($map)->first();
                if (!$childrenModel) {
                    $datum[$foreignKey] = $parentModel->$parentPk;
                    $childrenModel = $this->create($datum);
                    if (!$childrenModel->exists) {
                        throw new InternalException(__('message.updated.failed'));
                    }
                } else {
                    if (!$childrenModel->fill($datum)->save()) {
                        throw new InternalException(__('message.updated.failed'));
                    }
                }
            }
        }

        return $parentModel;
    }

    /**
     * 一对一数据同步
     * @param Model $parentModel
     * @param array $data
     * @param string $bind 一对一方法名
     * @return Model
     */
    public function oneSync(Model $parentModel, array $data, string $bind)
    {
        if ($parentModel->$bind) {
            if (!$parentModel->$bind->fill($data)->save()) {
                throw new InvalidRequestException(__('message.stored.failed'));
            }
        } else {
            $attrResultModel = $parentModel->$bind()->create($data);
            if (!$attrResultModel->exists) {
                throw new InvalidRequestException(__('message.stored.failed'));
            }
        }

        return $parentModel;
    }
}